package org.apache.osgimaker.editors.model;

import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.apache.osgimaker.api.IPersistableBundleListModel;
import org.apache.osgimaker.editors.conversions.BundleObjectListConverter;
import org.apache.osgimaker.editors.conversions.CollectionFormatter;
import org.apache.osgimaker.editors.conversions.Converter;
import org.eclipse.jface.text.IDocument;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

public class BundleListEditModel implements IPersistableBundleListModel {

	private static final String UTF8 = "UTF-8";

	private final DocumentBuilderFactory domfac = DocumentBuilderFactory
			.newInstance();
	private DocumentBuilder dombuilder = null;

	private List<Bundle> bundleList = null;
	private static final String BUNDLELIST = "bundleList";

	@SuppressWarnings("deprecation")
	private static final String[] KNOWN_PROPERTIES = new String[] { BUNDLELIST };

	private final PropertyChangeSupport propChangeSupport = new PropertyChangeSupport(
			this);
	private final Map<String, Object> objectProperties = new HashMap<String, Object>();
	private final Map<String, Object> changesToSave = new HashMap<String, Object>();
	private final Map<String, Bundle> generalInfoChange = new HashMap<String, Bundle>();
	private final Map<String, Bundle> classesInfoChange = new HashMap<String, Bundle>();

	Converter<List<String>, Object> listConverter = new BundleObjectListConverter();
	Converter<List<Bundle>, Object> stringListFormatter = new CollectionFormatter();
	private Properties properties = new Properties();

	@Override
	public void loadFrom(IDocument document) throws IOException {
		if (bundleList == null)
			bundleList = new ArrayList<Bundle>();
		else
			bundleList.clear();

		InputStream stream = new ByteArrayInputStream(document.get().getBytes(
				UTF8));

		try {
			dombuilder = domfac.newDocumentBuilder();
			Document doc = dombuilder.parse(stream);
			Element root = doc.getDocumentElement();
			NodeList bundles = root.getChildNodes();
			if (bundles != null) {
				for (int i = 0; i < bundles.getLength(); i++) {
					Node bundle = bundles.item(i);
					if (bundle.getNodeType() == Node.ELEMENT_NODE
							&& bundle.getNodeName().trim().equals("bundle")) {
						Bundle bund = new Bundle();

						Node id = bundle.getAttributes().getNamedItem("id");
						if (id != null)
							bund.setId(id.getNodeValue());
						Node symbolicName = bundle.getAttributes().getNamedItem("symbolicName");
						if (symbolicName != null)
							bund.setSymbolicName(symbolicName.getNodeValue());
						Node version = bundle.getAttributes().getNamedItem("version");
						if (version != null)
							bund.setVersion(version.getNodeValue());
						Node activator = bundle.getAttributes().getNamedItem("activator");
						if (activator != null)
							bund.setActivator(activator.getNodeValue());
						NodeList children = bundle.getChildNodes();
						if (children != null) {
							for (int j = 0; j < children.getLength(); j++) {
								Node child = children.item(j);
								if (child.getNodeType() == Node.ELEMENT_NODE) {
									String content = child.getTextContent();
									if (child.getNodeName().trim()
											.equals("classes")) {
										List<String> classes = new ArrayList<String>();
										if(content !=null && !content.trim().equals("")){
											String[] classList = content.split(",");
											for (int z = 0; z < classList.length; z++)
												classes.add(classList[z]);
										}
										bund.setClasses(classes);
									} else if (child.getNodeName().trim()
											.equals("usesExternal")) {
										List<String> privatePackages = new ArrayList<String>();
										String[] privateList = content
												.split(",");
										for (int z = 0; z < privateList.length; z++)
											if(!privateList[z].trim().equals(""))
												privatePackages.add(privateList[z].trim());
										bund.usedExternalList = privatePackages;
									} else if (child.getNodeName().trim()
											.equals("usesInternal")) {
										List<String> importPackages = new ArrayList<String>();
										String[] list = content.split(",");
										for (int z = 0; z < list.length; z++)
											if(!list[z].trim().equals(""))
												importPackages.add(list[z].trim());
										bund.usesInternalList = importPackages;
									} else if (child.getNodeName().trim()
											.equals("usedBy")) {
										List<String> exportPackages = new ArrayList<String>();
										String[] list = content.split(",");
										for (int z = 0; z < list.length; z++)
											if(!list[z].trim().equals(""))
												exportPackages.add(list[z].trim());
										bund.usedByList = exportPackages;
									} else if (child.getNodeName().trim()
											.equals("requireBundles")) {
										List<Bundle> requireBundles = new ArrayList<Bundle>();
										String[] list = content.split(",");
										for (int z = 0; z < list.length; z++)
											if(!list[z].trim().equals("")){
												Bundle n = new Bundle();
												n.setId(list[z].trim());
												requireBundles.add(n);
											}
										bund.sonBundleList = requireBundles;
									}
								}
							}
						}
						this.bundleList.add(bund);
					}
				}
			}
		} catch (ParserConfigurationException e) {
			e.printStackTrace();
		} catch (SAXException e) {
			e.printStackTrace();
		}

		properties.put(BUNDLELIST, bundleList);

		// Fire property changes on all known property names
		for (String prop : KNOWN_PROPERTIES) {
			// null values for old and new forced the change to be fired
			propChangeSupport.firePropertyChange(prop, null, null);
		}
	}

	public List<String> getBundleList() {
		return doGetObject(BUNDLELIST, listConverter);
	}
	
	public Bundle getBundleById(String bundleId){
		return doGetBundle(BUNDLELIST, bundleId);
	}
	
	public void setBundleList(List<? extends String> bundles) {
		List<String> oldBundles = getBundleList();
		List<Bundle> bundleObjectList = new ArrayList<Bundle>();
		for (int i = 0; i < bundles.size(); i++) {
			Bundle bnd = new Bundle();
			bnd.setId(bundles.get(i));
			bundleObjectList.add(bnd);
		}
		doSetObject(BUNDLELIST, oldBundles, bundleObjectList,
				stringListFormatter);
	}
	public void updateBundleGeneralInfo(String bundleId,Bundle bundle){
		generalInfoChange.put(bundleId, bundle);
	}
	public Bundle getChangedBundleGeneralInfo(String bundleId){
		return generalInfoChange.get(bundleId);
	}
	
	public void updateBundleClasses(String bundleId,Bundle bundle){
		classesInfoChange.put(bundleId, bundle);
	}
	
	public Bundle getChangedBundleClasses(String bundleId){
		return classesInfoChange.get(bundleId);
	}

	@Override
	public void saveChangesTo(IDocument document) {
		List<Bundle> newList = (List<Bundle>) changesToSave.get(BUNDLELIST);
		List<Bundle> oldList = (List<Bundle>) properties.get(BUNDLELIST);
		if (newList == null) newList = oldList;
			
		if (oldList != null)
			for (int j = 0; j < newList.size(); j++)
				for (int i = 0; i < oldList.size(); i++)
				 {
					Bundle old = oldList.get(i);
					Bundle newB = newList.get(j);
					if (newB.getId().equals(old.getId())) {
						Bundle basicInfo = getChangedBundleGeneralInfo(newB.getId());
						if(basicInfo != null ){
							old.setSymbolicName(basicInfo.getSymbolicName());
							old.setVersion(basicInfo.getVersion());
							old.setActivator(basicInfo.getActivator());
						}
						Bundle classesInfo = getChangedBundleClasses(newB.getId());
						if(classesInfo !=null ){
							old.setClasses(classesInfo.getClasses());
						}
						newList.set(j, old);
					}
				}

		StringBuilder buffer = new StringBuilder(
				"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<bundles>\n");
		for (int j = 0; j < newList.size(); j++) {
			buffer.append(getStringOfBundle(newList.get(j)));
		}
		
		buffer.append("\n</bundles>");
		document.set(buffer.toString());
		
		generalInfoChange.clear();
		properties.put(BUNDLELIST, newList);
		changesToSave.clear();
	}

	public String getStringOfBundle(Bundle bundle) {
		String buffer = "<bundle id=\"$idValue$\" " +
				"symbolicName=\"$symbolicName$\" " +
				"version=\"$version$\" " +
				"activator=\"$activator$\" >\n"
				+ "<classes>\n\t$classList$\n</classes>\n"
				+ "<usesExternal>\n\t$usesExternal$\n</usesExternal>\n"
				+ "<usesInternal>\n\t$usesInternal$\n</usesInternal>\n"
				+ "<usedBy>\n\t$usedBy$\n</usedBy>\n"
				+ "<requireBundles>\n\t$requireBundles$\n</requireBundles>\n</bundle>\n";

		buffer = buffer.replace("$idValue$", bundle.getId().trim());
		buffer = buffer.replace("$symbolicName$", bundle.getSymbolicName()==null?"":bundle.getSymbolicName().trim());
		buffer = buffer.replace("$version$", bundle.getVersion()==null?"":bundle.getVersion().trim());
		buffer = buffer.replace("$activator$", bundle.getActivator()==null?"":bundle.getActivator().trim());
		buffer = buffer.replace("$classList$", getClassesString(bundle).trim());
		buffer = buffer.replace("$usesExternal$", getUsesExternal(bundle).trim());
		buffer = buffer.replace("$usesInternal$", getUsesInternal(bundle).trim());
		buffer = buffer.replace("$usedBy$", getUsedBy(bundle).trim());
		buffer = buffer.replace("$requireBundles$", getRequireBundles(bundle).trim());
		return buffer;
	}
	private String getRequireBundles(Bundle bundle){
		if (bundle != null && bundle.sonBundleList != null) {
			String ret = "";
			for(Bundle b: bundle.sonBundleList){
					ret += b.getId() + ",\n\t";
			}
			return ret;
		} else
			return "";
	}
	
	private String getUsedBy(Bundle bundle){
		Collections.sort(bundle.usedByList);
		if (bundle != null && bundle.usedByList != null) {
			String ret = "";
			for(String uses: bundle.usedByList){
				if(!uses.trim().equals("(default package)") && !uses.startsWith("java."))
					ret += uses + ",\n\t";
			}
			return ret;
		} else
			return "";
	}
	
	private String getUsesInternal(Bundle bundle){
		Collections.sort(bundle.usesInternalList);
		if (bundle != null && bundle.usesInternalList != null) {
			String ret = "";
			for(String uses: bundle.usesInternalList){
				if(!uses.trim().equals("(default package)") && !uses.startsWith("java."))
					ret += uses + ",\n\t";
			}
			return ret;
		} else
			return "";
	}
	
	private String getUsesExternal(Bundle bundle){
		Collections.sort(bundle.usedExternalList);
		if (bundle != null && bundle.usedExternalList != null) {
			String ret = "";
			for(String uses: bundle.usedExternalList){
				if(!uses.trim().equals("(default package)") && !uses.startsWith("java."))
					ret += uses + ",\n\t";
			}
			return ret;
		} else
			return "";
	} 
	private String getClassesString(Bundle bundle) {
		if (bundle != null && bundle.getClasses() != null) {
			String ret = "";
			for (int i = 0; i < bundle.getClasses().size(); i++) {
				ret += bundle.getClasses().get(i).trim();
				if (i != bundle.getClasses().size() - 1)
					ret += ",\n\t";
			}

			return ret;
		} else
			return "";
	}
	
	private Bundle doGetBundle(String name,String bundleId){
		Bundle result = null;
		List<Bundle> list = null;
		if (changesToSave.containsKey(name)) {
			list = (List<Bundle>)changesToSave.get(name);

		} else if (properties.containsKey(name)) {
			list = (List<Bundle>)properties.get(name);
		} 
		if(list != null ){
			for(int i=0;i<list.size();i++){
				Bundle each = list.get(i);
				if(each.getId().equals(bundleId)) {
					result = each;
					break;
				}
			}
		}
		return result;
	}

	<R> R doGetObject(String name,
			Converter<? extends R, ? super Object> converter) {
		R result;
		// /if (objectProperties.containsKey(name)) {
		// @SuppressWarnings("unchecked")
		// R temp = (R) objectProperties.get(name);
		// result = temp;
		// } else
		if (changesToSave.containsKey(name)) {
			result = converter.convert(changesToSave.get(name));
			objectProperties.put(name, result);
		} else if (properties.containsKey(name)) {
			result = converter.convert(properties.get(name));
			objectProperties.put(name, result);
		} else {
			result = null;
		}
		return result;
	}

	<T> void doSetObject(String name, T oldValue, T newValue,
			Converter<List<Bundle>, ? super T> formatter) {
		objectProperties.put(name, newValue);
		changesToSave.put(name, formatter.convert(newValue));
		propChangeSupport.firePropertyChange(name, oldValue, newValue);
	}

	public void addPropertyChangeListener(PropertyChangeListener listener) {
		propChangeSupport.addPropertyChangeListener(listener);
	}

	public void addPropertyChangeListener(String propertyName,
			PropertyChangeListener listener) {
		propChangeSupport.addPropertyChangeListener(propertyName, listener);
	}

	public void removePropertyChangeListener(PropertyChangeListener listener) {
		propChangeSupport.removePropertyChangeListener(listener);
	}

	public void removePropertyChangeListener(String propertyName,
			PropertyChangeListener listener) {
		propChangeSupport.removePropertyChangeListener(propertyName, listener);
	}
	
	public List<Bundle> getDataModelBundleList(){
		return this.bundleList;
	}
}
